📄

RVC SW Controller v2.0

전체 프로젝트 문서 — 21개 파일 (v2.0 수정: 16개)
오른쪽 센서 삭제 · 전방 센서 회전 스캔 대체 · 장애물 회피 좌측 우선
프로젝트 개요
CLAUDE.md
CLAUDEv2.0 수정

현재 작업: v2.0 — 아래 작업 지시를 최우선으로 따른다

v2.0 작업 지시 (오른쪽 센서 삭제)

변경 요약

  • 오른쪽 전용 센서를 삭제한다. 우측 장애물 감지는 전방 센서 회전 스캔으로 대체한다.
  • 장애물 회피 방향은 랜덤 선택에서 좌측 우선으로 변경한다. (좌측이 막힌 경우에만 우측)

작업 규칙

  1. 기존 파일을 읽고 수정한다. 새 파일을 만들지 않는다.
  2. 수정된 모든 .md 파일에는 변경 내용을 아래 태그로 빠짐없이 표시한다.
  3. 추가 — 새로 추가된 내용
  4. 삭제 — 제거된 내용 (삭제된 원문을 취소선으로 남긴다)
  5. 변경 — 기존 내용이 수정된 경우

이 구분선 아래는 v1.0 기준 문서다. 위 v2.0 작업 지시와 충돌 시 v2.0을 우선 적용한다.


RVC SW Controller 프로젝트

프로젝트 개요

로봇 청소기(RVC) SW Controller를 OOAD 프로세스로 개발한다.

요구사항

  • RVC는 가정의 표면을 자동으로 청소하고 닦는다
  • 청소하면서 직진한다
  • 전방 센서가 장애물을 감지하면 청소를 멈추고, 좌/우로 방향을 틀어 청소하며 전진한다
  • 앞/좌/우 모두 장애물이 있으면 후진 후 좌/우로 방향을 틀어 전진한다
  • 먼지를 감지하면 잠시 청소 출력을 높인다
  • HW 제어의 상세 설계 및 구현은 고려하지 않는다
  • 자동 청소 기능에만 집중한다

폴더 구조

arch/requirements/     - 요구사항 문서
arch/usecases/         - Use Case 문서
arch/analysis/ssd/     - SSD 다이어그램
arch/analysis/domain/  - 도메인 모델
arch/design/SD/        - 시퀀스 다이어그램
arch/design/class/     - 클래스 다이어그램
arch/system-tests/     - 시스템 테스트
src/                   - 소스 코드
tests/                 - 테스트 코드
simulator/             - RVC 동작 시뮬레이터 (유일하게 객체지향으로 개발하지 않아도 됨)

진행 단계

  1. 요구사항 분석: arch/system.md, arch/requirements/fr-nfr.md
  2. OOA: arch/usecases/usecases.mdarch/usecases/UC-nnn.mdarch/analysis/ssd/ssd.mdarch/analysis/domain/domain.md
  3. OOD: arch/design/SD/SD.mdarch/design/class/class-diagram.md
  4. OOI: src/tests/arch/system-tests/system-tests.mdsimulator/

설계 방향

  • 구동 모터, 청소 모터는 외부 액터로 취급한다
  • 시스템 내부에는 모터를 제어하는 별도 컨트롤러 객체를 둔다 (예: MotorController, CleaningController)
  • 추가 오른쪽 전용 센서는 존재하지 않는다. 우측 장애물 감지는 전방 센서를 이용한 회전 스캔으로 대체한다.

규칙

  • 모든 다이어그램은 Mermaid 형식으로 작성한다
  • 모든 코드는 객체지향 원칙을 철저히 준수한다 (simulator 제외)
  • simulator를 통하여 system-tests를 테스트한다
  • 변경 장애물 회피 시 좌/우 방향은 랜덤으로 선택한다 → 장애물 회피 시 좌측을 우선으로 선택한다. 좌측이 막힌 경우에만 우측으로 전환한다.
  • usecases, ssd, domain-model, ssd, class-diagram 은 반드시 통일성을 가질 것
  • 구현 언어: C++

추가 변경 이력

버전 변경 내용 날짜
v2.0 오른쪽 센서 삭제 — 전방 센서 회전 스캔으로 우측 감지 대체, 장애물 회피 방향 좌측 우선으로 변경 2025-05-29
arch/system.md
systemv2.0 수정

시스템 정의 (System Definition)

1. 시스템 목적

RVC(Robot Vacuum Cleaner) SW Controller는 로봇 청소기가 가정의 바닥을 자동으로 청소하고 닦을 수 있도록 제어하는 소프트웨어 시스템이다.

2. 시스템 범위

포함 범위

  • 자동 주행 및 청소 제어 로직
  • 장애물 감지에 따른 방향 전환 로직
  • 먼지 감지에 따른 청소 출력 조절 로직
  • 센서 데이터 수신 및 처리
  • HW 제어 인터페이스 (추상화 수준)

제외 범위

  • HW 드라이버 상세 설계 및 구현
  • 수동 조작(리모컨, 앱 제어) 기능
  • 배터리 관리 및 충전 복귀 기능
  • 맵핑 및 경로 계획 기능

3. 시스템 컨텍스트

+------------------+       센서 데이터       +---------------------+
|   장애물 센서     | ----------------------> |                     |
+------------------+                       |   RVC SW Controller |
                                           |                     |
+------------------+       센서 데이터        |   (자동 청소 제어)     |
|   먼지 센서      | ----------------------> |                     |
+------------------+                        +---------------------+
                                                      |
                                          모터/청소 제어 명령
                                                      |
                              +-----------+-----------+
                              |           |           |          
                         +--------+  +--------+  +--------+
                         | 구동   |  | 청소   |    | ...  |
                         | 모터   |  | 모터   |    |      |
                         +--------+  +--------+  +--------+  

4. 주요 요구사항 요약

ID 요구사항
SR-01 RVC는 가정의 표면을 자동으로 청소하고 닦는다
SR-02 청소하면서 직진한다
SR-03 변경 센서가 전방 센서가 장애물을 감지하면 청소를 멈추고, 좌/우로 방향을 틀어 청소하며 전진한다
SR-04 앞/좌/우 모두 장애물이 있으면 후진 후 좌/우로 방향을 틀어 전진한다
SR-05 먼지를 감지하면 잠시 청소 출력을 높인다

5. 시스템 외부 인터페이스

인터페이스 방향 설명
장애물 센서 입력 변경 전방/좌/우 장애물 감지 신호 전방·좌측 장애물 감지 신호 (우측 전용 센서 없음, 우측 감지는 전방 센서 회전 스캔으로 대체)
먼지 센서 입력 먼지 감지 신호
구동 모터 출력 전진/후진/정지/방향 전환 제어
청소 모터 출력 청소 출력 조절 (Normal / High)

6. 핵심 동작 시나리오

  1. 정상 청소: RVC가 전원을 켜면 직진하며 청소 및 닦기를 수행한다.
  2. 장애물 회피 (측면 여유 있음): 전방 장애물 감지 → 청소 정지 → 변경 좌 또는 우로 회전 좌측 우선으로 회전 (좌측이 막힌 경우에만 우측) → 청소 재개 및 전진.
  3. 장애물 회피 (삼면 막힘): 전/좌/우 장애물 모두 감지 → 후진(이때 좌 또는 우에 막히지 않는 방향이 나올 때까지 후진한다) → 변경 좌 또는 우로 회전 좌측 우선으로 회전 (좌측이 막힌 경우에만 우측) → 전진.
  4. 고출력 청소: 먼지 감지 → 청소 출력 High 전환 → 일정 시간 경과 후 Normal 복귀.

7. 개발 단계

단계 산출물
요구사항 분석 arch/system.md, arch/requirements/fr-nfr.md
OOA arch/usecases/usecases.md, arch/usecases/UC-nnn.md, arch/analysis/ssd/ssd.md, arch/analysis/domain/domain.md
OOD arch/design/sd/sd.md, arch/design/class/class-diagram.md
OOI src/, tests/, simulator/, arch/system-tests/system-tests.md
요구사항
arch/requirements/fr-nfr.md
fr-nfrv2.0 수정

기능/비기능 요구사항 (Functional / Non-Functional Requirements)

1. 기능 요구사항 (Functional Requirements)

FR-01: 자동 청소 및 닦기

  • 설명: RVC는 전원이 켜지면 바닥을 자동으로 청소(흡입)하고 닦으면서 직진한다.
  • 출처: SR-01, SR-02
  • 입력: 전원 ON 이벤트
  • 출력: 구동 모터 전진, 청소 모터 Normal 출력 동작
  • 조건: -

FR-02: 전방 장애물 감지 시 방향 전환

  • 설명: 전방에 장애물이 감지되고 좌 또는 우 중 하나라도 여유 공간이 있으면, 청소를 멈추고 여유 있는 방향으로 회전한 뒤 청소를 재개하며 전진한다.
  • 출처: SR-03
  • 입력: 변경 전방 장애물 감지 신호 (장애물 센서) 전방 장애물 감지 신호 (전방 센서); 우측 여유 공간 감지 (전방 센서 회전 스캔으로 대체, 우측 전용 센서 없음); 좌측 여유 공간 감지 (좌측 센서)
  • 출력: 청소 모터 정지 → 구동 모터 회전(좌 또는 우) → 청소 모터 재개 및 구동 모터 전진
  • 조건: 좌/우 중 적어도 한 방향에 장애물이 없어야 한다
  • 선택 정책: 변경 좌/우 모두 여유 있으면 랜덤으로 방향을 선택한다 좌측을 우선으로 선택한다. 좌측이 막힌 경우에만 우측으로 선택한다.

FR-03: 삼면 장애물 감지 시 후진 후 방향 전환

  • 설명: 전방·좌·우 모두 장애물이 감지되면 후진하고, 좌 또는 우에 막히지 않는 방향이 나올 때까지 후진한 뒤 해당 방향으로 회전하여 전진한다.
  • 출처: SR-04
  • 입력: 변경 전방·좌·우 장애물 감지 신호 동시 수신 전방·좌측 장애물 감지 신호 동시 수신 (우측 장애물 감지는 전방 센서 회전 스캔으로 대체, 우측 전용 센서 없음)
  • 출력: 구동 모터 후진 → (좌 또는 우 여유 공간 감지 시) 구동 모터 회전 → 전진
  • 조건: 후진은 좌 또는 우 중 하나의 장애물이 해소될 때까지 계속한다
  • 선택 정책: 변경 후진 중 좌/우 모두 동시에 여유 공간이 확보되면 랜덤으로 방향을 선택한다 후진 중 좌/우 여유 공간이 확보되면 좌측을 우선으로 선택한다. 좌측이 막힌 경우에만 우측으로 선택한다.

FR-04: 먼지 감지 시 청소 출력 강화

  • 설명: 먼지 센서가 먼지를 감지하면 청소 모터 출력을 High로 높이고, 일정 시간이 경과하면 Normal 출력으로 복귀한다.
  • 출처: SR-05
  • 입력: 먼지 감지 신호 (먼지 센서)
  • 출력: 청소 모터 출력 Normal → High → (일정 시간 후) Normal
  • 조건: 출력 강화 지속 시간은 별도 파라미터로 관리한다

2. 비기능 요구사항 (Non-Functional Requirements)

NFR-01: 실시간 응답성

  • 설명: 센서 이벤트(장애물, 먼지 감지) 수신 후 제어 명령 발행까지의 지연이 허용 범위 이내여야 한다.
  • 기준: 센서 이벤트 발생 후 100ms 이내에 제어 명령 발행

NFR-02: 안전성

  • 설명: 알 수 없는 상태나 예외 상황에서 RVC는 안전한 상태(정지)로 전환되어야 한다.
  • 기준: 예외 발생 시 구동 모터 및 청소 모터를 즉시 정지한다

NFR-03: 테스트 가능성

  • 설명: 모든 기능 요구사항은 단위 테스트 및 시스템 테스트로 검증 가능해야 한다.
  • 기준: FR-01 ~ FR-04 각각에 대한 시스템 테스트 시나리오가 존재한다 (arch/system-tests/system-tests.md)

NFR-04: 유지보수성

  • 설명: HW 인터페이스(센서, 모터)는 추상화 계층으로 분리되어 SW 로직 변경 없이 교체 가능해야 한다.
  • 기준: 센서 및 모터 제어는 인터페이스(추상 클래스 또는 프로토콜)를 통해서만 접근한다

NFR-05: 시뮬레이션 지원

  • 설명: 실제 HW 없이 SW 로직을 검증할 수 있는 시뮬레이터를 지원한다.
  • 기준: simulator/ 디렉터리에 HW 인터페이스 스텁(Stub) 구현이 존재한다

3. 요구사항 추적 매트릭스

시스템 요구사항 기능 요구사항 비기능 요구사항
SR-01 (청소 및 닦기) FR-01 NFR-01, NFR-02
SR-02 (직진) FR-01 NFR-01
SR-03 (장애물 → 방향 전환) FR-02 NFR-01, NFR-02
SR-04 (삼면 장애물 → 후진) FR-03 NFR-01, NFR-02
SR-05 (먼지 → 출력 강화) FR-04 NFR-01
Use Cases
arch/usecases/usecases.md
usecasesv2.0 수정

Use Case 목록 (Use Cases)

1. 액터 (Actors)

액터 종류 설명
User Primary Actor 시스템을 시작시키는 주체
ObstacleSensor Primary Actor 변경 전방/좌/우 장애물 감지 신호를 시스템에 전달한다 전방/좌 장애물 감지 신호를 시스템에 전달한다. 우측 장애물 감지는 전방 센서 회전 스캔으로 대체한다 (우측 전용 센서 없음).
DustSensor Primary Actor 먼지 감지 신호를 시스템에 전달한다
DriveMotor Secondary Actor 시스템으로부터 주행 명령(전진/후진/회전/정지)을 수신한다
CleaningMotor Secondary Actor 시스템으로부터 청소 출력 명령(Normal/High/정지)을 수신한다

2. Use Case 다이어그램

RVC SW Controller

start

frontObstacleDetected

dustDetected

«extend»

[변경] «extend» E1

«extend»

User«actor»

ObstacleSensor«actor»

DustSensor«actor»

DriveMotor«actor»

CleaningMotor«actor»

UC-001
자동 청소 수행

UC-002
측면 장애물 회피

UC-003
삼면 장애물 후진 회피

UC-004
고출력 청소

3. Use Case 목록

Use Case ID 이름 관련 액터 관련 요구사항 상세 문서
UC-001 자동 청소 수행 User, DriveMotor, CleaningMotor FR-01 UC-001.md
UC-002 측면 장애물 회피 ObstacleSensor, DriveMotor, CleaningMotor FR-02 UC-002.md
UC-003 삼면 장애물 후진 회피 변경 ObstacleSensor, DriveMotor ObstacleSensor, DriveMotor, CleaningMotor FR-03 UC-003.md
UC-004 고출력 청소 DustSensor, CleaningMotor FR-04 UC-004.md

4. Use Case 요약

UC-001: 자동 청소 수행

User가 시스템을 시작하면 CleaningMotor를 Normal 출력으로 가동하고 DriveMotor로 직진하며 바닥을 청소하고 닦는다. 이 동작은 시스템의 기본(Base) 흐름이며, 나머지 UC는 이를 확장(extend)한다.

UC-002: 측면 장애물 회피

전방에 장애물이 감지되면 청소를 일시 정지하고, 변경 좌 또는 우 중 여유 공간이 있을 때 sideStatus(LEFT) 및 rotateRight() + sideStatus(RIGHT) 로 양측을 순차 확인한다. 변경 좌·우 모두 여유 있으면 랜덤으로 방향을 선택한다. 좌측을 우선으로 선택한다. 좌측이 막힌 경우에만 우측으로 선택한다. 좌·우 모두 막힌 경우 E1으로 UC-003에 진입한다.

UC-003: 삼면 장애물 후진 회피

변경 전방·좌·우 모두 장애물이 감지될 때 ObstacleSensor가 직접 트리거한다. UC-002 E1에서 진입한다 (sideStatus(LEFT, blocked) + sideStatus(RIGHT, blocked) 내부 판정). 후진하며 sideStatus(LEFT) 또는 rotateRight() + sideStatus(RIGHT)로 여유 공간을 확인하고, 확보되면 변경 랜덤으로 좌측을 우선으로 선택하여 회전하고 전진한다.

UC-004: 고출력 청소

먼지가 감지되면 CleaningMotor 출력을 High로 높이고, 일정 시간이 경과하면 자동으로 Normal 출력으로 복귀한다. 주행은 중단 없이 계속한다.

5. Use Case 간 관계

«extend»
[변경] frontObstacleDetected

«extend»
[변경] E1: 좌·우 모두 막힘

«extend»
[dustDetected]

UC-001
자동 청소 수행
(Base)

UC-002
측면 장애물 회피

UC-003
삼면 장애물 후진 회피

UC-004
고출력 청소

arch/usecases/UC-001.md
UC-001v2.0 수정

UC-001: 자동 청소 수행

기본 정보

항목 내용
Use Case ID UC-001
이름 자동 청소 수행
액터 User (Primary), DriveMotor (Secondary), CleaningMotor (Secondary)
관련 요구사항 FR-01, SR-01, SR-02
종류 Base Use Case

사전 조건 (Preconditions)

  • User가 RVC를 시작시킨다.

사후 조건 (Postconditions)

  • RVC가 CleaningMotor Normal 출력으로 직진하며 청소 및 닦기를 수행하고 있다.

기본 흐름 (Main Flow)

단계 액터/시스템 행위
1 User RVC를 시작시킨다.
2 System CleaningMotor를 Normal 출력으로 가동한다.
3 System DriveMotor를 전진 방향으로 구동한다.
4 System 직진하며 바닥을 청소하고 닦는다.
5 System 단계 4를 계속 반복한다.

확장 포인트 (Extension Points)

확장 포인트 조건 확장 UC
EP-1 변경 frontObstacleDetected AND (좌 OR 우 여유 있음) frontObstacleDetected (좌·우 여유 공간 확인은 UC-002 내에서 sideStatus(LEFT) → rotateRight() + sideStatus(RIGHT) 순으로 수행) UC-002
삭제 EP-2 UC-003 (UC-002 E1 예외 흐름으로 이동)
EP-3 dustDetected UC-004

활동 다이어그램

frontObstacleDetected

[삭제]

dustDetected

없음

User: start()

CleaningMotor: Normal ON

DriveMotor: moveForward

이벤트 감지?

EP-1: UC-002 확장

[삭제] ~~EP-2: UC-003 확장~~ (UC-002 E1으로 이동)

EP-3: UC-004 확장

arch/usecases/UC-002.md
UC-002v2.0 수정

UC-002: 측면 장애물 회피

기본 정보

항목 내용
Use Case ID UC-002
이름 측면 장애물 회피
액터 ObstacleSensor (Primary), DriveMotor (Secondary), CleaningMotor (Secondary)
관련 요구사항 FR-02, SR-03
종류 Extension of UC-001 (EP-1)

사전 조건 (Preconditions)

  • UC-001이 실행 중이다.
  • 전방에 장애물이 감지되었다.
  • 변경 좌 또는 우 중 하나 이상의 방향에 여유 공간이 있다. 좌·우 여유 공간 여부는 이 UC 내부에서 순차 확인한다 (진입 시점에 미결정).

사후 조건 (Postconditions)

  • 변경 RVC가 장애물이 없는 방향(좌 또는 우)으로 회전한 뒤, 청소를 재개하며 전진하고 있다. (정상) 좌 또는 우로 회전 후 청소를 재개하며 전진한다. / (예외 E1) 좌·우 모두 막힌 경우 UC-003으로 전환한다.

기본 흐름 (Main Flow)

단계 액터/시스템 행위
1 ObstacleSensor 전방 장애물 감지 신호를 시스템에 전달한다.
2 System CleaningMotor를 정지한다.
3 System DriveMotor를 정지한다.
4 ObstacleSensor 변경 좌측 장애물 유무 신호를 시스템에 전달한다. sideStatus(LEFT, status) — 좌측 센서가 좌측 장애물 유무 신호를 시스템에 전달한다.
5 System 추가 DriveMotor에 rotateRight()를 명령한다 (전방 센서를 우측으로 회전).
6 ObstacleSensor 추가 sideStatus(RIGHT, status) — 전방 센서(우측 향함)가 우측 장애물 유무 신호를 시스템에 전달한다.
7 System 추가 DriveMotor에 rotateLeft()를 명령한다 (정면 복귀). 이후 turn()이 원래 전방 기준으로 동작하도록 보장한다.
8 System 변경 여유 있는 방향(좌 또는 우)으로 DriveMotor를 회전시킨다. 좌측이 여유 있으면 좌회전한다. 좌측이 막히고 우측이 여유 있으면 우회전한다.
9 System CleaningMotor를 Normal 출력으로 재가동한다.
10 System DriveMotor를 전진 방향으로 구동한다.
11 UC-001 기본 흐름 단계 4로 복귀한다.

대안 흐름 (Alternative Flow)

변경 A1: 좌·우 모두 여유 있을 때 A1: 좌측 여유 우선 선택

  • 변경 단계 5에서 좌·우 모두 여유 있으면 랜덤으로 방향을 선택하여 회전한다. 단계 7에서 좌측이 여유 있으면 좌측을 우선으로 선택한다. 좌·우 모두 여유 있더라도 항상 좌측을 선택한다.

예외 흐름 (Exception Flow)

E1: 변경 회피 도중 전/좌/우 모두 막힘 좌·우 모두 막힘 확인 후 UC-003 전환

  • 변경 단계 5 이전에 삼면이 모두 막힌 것이 감지되면 UC-003으로 전환한다. 단계 7(rotateLeft 정면 복귀) 완료 후 sideStatus(LEFT)와 sideStatus(RIGHT) 모두 막힘이면 UC-003으로 전환한다.

활동 다이어그램

좌측 여유 (우선)

좌측 막힘 · 우측 여유

[변경] 좌·우 모두 막힘

UC-001 EP-1 진입
(frontObstacleDetected)

CleaningMotor: 정지

DriveMotor: 정지

[변경] ObstacleSensor: sideStatus(LEFT, status)

[추가] DriveMotor: rotateRight()

[추가] ObstacleSensor: sideStatus(RIGHT, status)

[추가] DriveMotor: rotateLeft() (정면 복귀)

방향 결정

DriveMotor: 좌회전

DriveMotor: 우회전

UC-003 전환 (E1)

CleaningMotor: Normal 재가동

DriveMotor: 전진

UC-001 기본 흐름 복귀

arch/usecases/UC-003.md
UC-003v2.0 수정

UC-003: 삼면 장애물 후진 회피

기본 정보

항목 내용
Use Case ID UC-003
이름 삼면 장애물 후진 회피
액터 ObstacleSensor (Primary), DriveMotor (Secondary), 추가 CleaningMotor (Secondary)
관련 요구사항 FR-03, SR-04
종류 변경 Extension of UC-001 (EP-2) Extension of UC-002 (E1)

사전 조건 (Preconditions)

  • UC-001 및 UC-002가 실행 중이다.
  • 변경 전방·좌·우 방향 모두에 장애물이 감지되었다. UC-002 내에서 sideStatus(LEFT, blocked) 및 sideStatus(RIGHT, blocked) 가 모두 수신되어 좌·우 모두 막힘이 확인되었다 (UC-002 E1). 우측 전용 센서 없음.

사후 조건 (Postconditions)

  • RVC가 후진 중 확보된 여유 방향(좌 또는 우)으로 회전한 뒤 전진하고 있다.

기본 흐름 (Main Flow)

단계 액터/시스템 행위
1 (UC-002 E1) 변경 ObstacleSensor: 전방·좌측 장애물 감지 신호를 전달한다. 좌·우 모두 막힘이 확인된 상태로 UC-002 E1에서 진입한다. (CleaningMotor는 UC-002 단계 2에서 이미 정지됨.)
2 System DriveMotor를 후진 방향으로 구동한다.
3 ObstacleSensor 변경 좌측 장애물 유무 신호를 계속 전달한다. sideStatus(LEFT, status) — 좌측 센서가 좌측 장애물 유무 신호를 전달한다.
4 System 추가 좌측이 여전히 막힌 경우 DriveMotor에 rotateRight()를 명령한다 (전방 센서를 우측으로 회전).
5 ObstacleSensor 추가 sideStatus(RIGHT, status) — 전방 센서(우측 향함)가 우측 장애물 유무 신호를 전달한다.
6 System 추가 rotateRight()를 호출한 경우 DriveMotor에 rotateLeft()를 명령한다 (정면 복귀).
7 System 좌 또는 우에 여유 공간이 확보될 때까지 단계 2~6을 반복한다.
8 System DriveMotor를 정지한다.
9 System 변경 여유 공간이 확보된 방향(좌 또는 우)으로 DriveMotor를 회전시킨다. 좌측에 여유 공간이 확보된 경우 좌회전한다. 좌측이 막힌 경우에만 우측으로 DriveMotor를 회전시킨다.
10 System DriveMotor를 전진 방향으로 구동한다.
11 System CleaningMotor를 Normal 출력으로 재가동한다.
12 UC-001 기본 흐름 단계 4로 복귀한다.

대안 흐름 (Alternative Flow)

변경 A1: 후진 중 좌·우 동시에 여유 공간 확보 A1: 후진 중 좌측 여유 공간 우선 선택

  • 변경 단계 5에서 좌·우 모두 동시에 해소되면 랜덤으로 방향을 선택하여 회전한다. 단계 3에서 sideStatus(LEFT, clear)가 수신되면 단계 4~6을 건너뛰고 즉시 좌회전한다. 좌·우 모두 동시에 확보되더라도 항상 좌측을 우선으로 선택한다.

활동 다이어그램

아니오

아니오

[변경] UC-002 E1 진입
(좌·우 모두 막힘 확인됨)

DriveMotor: 후진

[변경] ObstacleSensor: sideStatus(LEFT, status)

좌측 여유?

DriveMotor: 정지

DriveMotor: 좌회전

[추가] DriveMotor: rotateRight()

[추가] ObstacleSensor: sideStatus(RIGHT, status)

[추가] DriveMotor: rotateLeft() (정면 복귀)

우측 여유?

DriveMotor: 정지

DriveMotor: 우회전

DriveMotor: 전진

CleaningMotor: Normal 재가동

UC-001 기본 흐름 복귀

arch/usecases/UC-004.md
UC-004

UC-004: 고출력 청소

기본 정보

항목 내용
Use Case ID UC-004
이름 고출력 청소
액터 DustSensor (Primary), CleaningMotor (Secondary)
관련 요구사항 FR-04, SR-05
종류 Extension of UC-001 (EP-3)

사전 조건 (Preconditions)

  • UC-001이 실행 중이다 (CleaningMotor Normal 출력, DriveMotor 전진 중).
  • DustSensor가 먼지를 감지하였다.

사후 조건 (Postconditions)

  • CleaningMotor가 Normal 출력으로 복귀하였다.
  • DriveMotor 전진은 UC-004 실행 중에도 중단 없이 유지되었다.

기본 흐름 (Main Flow)

단계 액터/시스템 행위
1 DustSensor 먼지 감지 신호를 시스템에 전달한다.
2 System CleaningMotor 출력을 High로 전환한다.
3 System 고출력 지속 타이머를 시작한다.
4 System DriveMotor 전진을 유지한다 (주행 중단 없음).
5 System 타이머가 만료될 때까지 단계 4를 반복한다.
6 System CleaningMotor 출력을 Normal로 복귀시킨다.
7 UC-001 기본 흐름 단계 4로 복귀한다.

대안 흐름 (Alternative Flow)

A1: 고출력 청소 중 장애물 감지

  • 단계 4 중 장애물이 감지되면 UC-002 또는 UC-003을 수행한다.
  • 장애물 회피 완료 후 남은 타이머 시간만큼 고출력 청소를 계속하고 단계 6으로 진행한다.

A2: 고출력 청소 중 추가 먼지 감지

  • 단계 5 중 먼지가 재감지되면 타이머를 초기화하여 단계 3부터 재시작한다.

활동 다이어그램

아니오

아니오

UC-001 EP-3 진입
(dustDetected)

CleaningMotor: High 출력 전환

고출력 타이머 시작

DriveMotor: 전진 유지

타이머 만료?

dustDetected 재감지?

CleaningMotor: Normal 복귀

UC-001 기본 흐름 복귀

분석 — SSD
arch/analysis/ssd/SSD-001.md
SSD-001

SSD-001: 자동 청소 수행

  • 연관 UC: UC-001
  • 관련 요구사항: FR-01, SR-01, SR-02

시스템 시퀀스 다이어그램

SystemSystem직진하며 청소 유지loop[청소 진행 중]UserCleaningMotorDriveMotorstart()startCleaning(Normal)moveForward()UserCleaningMotorDriveMotor

시스템 오퍼레이션 목록

오퍼레이션 설명
start() User가 시스템을 시작시킨다
arch/analysis/ssd/SSD-002.md
SSD-002v2.0 수정

SSD-002: 측면 장애물 회피

  • 연관 UC: UC-002
  • 관련 요구사항: FR-02, SR-03

시스템 시퀀스 다이어그램

SystemSystemUC-001 실행 중UC-003으로 전환alt[[E1] 좌·우 모두 막힘][좌측 여유 (좌측 우선)][우측만 여유]UC-001 기본 흐름 복귀ObstacleSensorCleaningMotorDriveMotorfrontObstacleDetected()stopCleaning()stop()sideStatus(LEFT, status)rotateRight()sideStatus(RIGHT, status)rotateLeft()turn(LEFT)startCleaning(Normal)moveForward()turn(RIGHT)startCleaning(Normal)moveForward()ObstacleSensorCleaningMotorDriveMotor

시스템 오퍼레이션 목록

오퍼레이션 설명
frontObstacleDetected() 전방 장애물 감지 신호를 수신한다
변경 sideStatus(available) sideStatus(LEFT, status) 변경 여유 공간이 있는 방향(Direction)의 목록을 수신한다 좌측 센서가 좌측 장애물 유무 신호를 전달한다. 회전 없이 직접 수신.
추가 sideStatus(RIGHT, status) DriveMotor.rotateRight() 이후 전방 센서(우측 향함)가 우측 장애물 유무 신호를 전달한다 (우측 전용 센서 없음).
추가 rotateLeft() sideStatus(RIGHT) 수신 후 DriveMotor를 정면으로 복귀시킨다. 이후 turn()이 항상 원래 전방 기준으로 동작하도록 보장한다.
arch/analysis/ssd/SSD-003.md
SSD-003v2.0 수정

SSD-003: 삼면 장애물 후진 회피

  • 연관 UC: UC-003
  • 관련 요구사항: FR-03, SR-04

시스템 시퀀스 다이어그램

SystemSystemUC-002 E1 진입 (좌·우 모두 막힘 확인됨)alt[좌측 막힘]loop[좌 또는 우 여유 공간 확보 시까지]alt[좌측 여유 (좌측 우선)][우측만 여유]UC-001 기본 흐름 복귀ObstacleSensorCleaningMotorDriveMotormoveBackward()sideStatus(LEFT, status)rotateRight()sideStatus(RIGHT, status)rotateLeft()stop()turn(LEFT)turn(RIGHT)moveForward()startCleaning(Normal)ObstacleSensorCleaningMotorDriveMotor

시스템 오퍼레이션 목록

오퍼레이션 설명
삭제 UC-002 내에서 sideStatus(LEFT) + sideStatus(RIGHT) 결과로 내부 판정 후 UC-003에 직접 진입. 별도 외부 오퍼레이션 없음.
변경 sideStatus(available) sideStatus(LEFT, status) 변경 후진 중 좌·우 여유 공간 목록 수신 후진 중 좌측 센서가 좌측 장애물 유무를 전달한다. 반복 수신.
추가 sideStatus(RIGHT, status) 좌측 막힘 시 rotateRight() 이후 전방 센서(우측 향함)가 우측 장애물 유무를 전달한다. 좌측 여유 확보 시 생략.
추가 rotateLeft() 좌측 막힘 시 sideStatus(RIGHT) 수신 후 DriveMotor를 정면으로 복귀시킨다. 루프 재진입 및 최종 turn() 모두 원래 전방 기준으로 동작하도록 보장한다.
arch/analysis/ssd/SSD-004.md
SSD-004

SSD-004: 고출력 청소

  • 연관 UC: UC-004
  • 관련 요구사항: FR-04, SR-05

시스템 시퀀스 다이어그램

SystemSystemUC-001 실행 중opt[먼지 재감지]loop[고출력 유지 중]UC-001 기본 흐름 복귀DustSensorCleaningMotordustDetected()startCleaning(High)dustDetected()startCleaning(Normal)DustSensorCleaningMotor

시스템 오퍼레이션 목록

오퍼레이션 설명
dustDetected() 먼지 감지 신호를 수신한다. 재감지 시 동일 오퍼레이션으로 타이머 초기화
분석 — 도메인 모델
arch/analysis/domain/domain.md
domainv2.0 수정

도메인 모델 (Domain Model)

starts

notifies

notifies

delegates

delegates

controls

controls

manages

onExpired

uses

uses

«actor»

User

«actor»

ObstacleSensor

«actor»

DustSensor

«actor»

DriveMotor

«actor»

CleaningMotor

RVCController

NavigationController

CleaningController

HighPowerTimer

duration : int

«enumeration»

Direction

LEFT

RIGHT

«enumeration»

CleaningLevel

NORMAL

HIGH

개념 클래스 설명

외부 액터

액터는 시스템이 상태를 소유·관리하는 객체가 아니므로 필드를 표현하지 않는다. 센서 데이터는 시스템 오퍼레이션의 파라미터로 전달된다. 변경 sideStatus(available) 좌·우 각각 sideStatus(LEFT, status) / sideStatus(RIGHT, status) 두 번의 별도 호출로 분리된다. 우측은 rotateRight() 이후 전방 센서가 전달한다.

클래스 단일 책임
User 시스템을 시작시키는 주체
ObstacleSensor 변경 장애물 감지 이벤트를 RVCController에 전달한다 전방·좌측 장애물 감지 이벤트를 RVCController에 전달한다. sideStatus(LEFT)는 좌측 센서가 직접 전달하고, sideStatus(RIGHT)는 DriveMotor.rotateRight() 이후 전방 센서가 전달한다 (우측 전용 센서 없음).
DustSensor 먼지 감지 이벤트를 RVCController에 전달한다
DriveMotor NavigationController의 주행 명령을 수행한다
CleaningMotor CleaningController의 청소 출력 명령을 수행한다

시스템 내부 객체

클래스 단일 책임
RVCController 변경 시스템 오퍼레이션(start, frontObstacleDetected, 삭제, sideStatus, dustDetected)을 수신한다. 시스템 오퍼레이션 (start, frontObstacleDetected, sideStatus(LEFT, status), sideStatus(RIGHT, status), dustDetected)을 수신하고 NavigationController·CleaningController에 위임한다.
NavigationController DriveMotor를 제어한다. 변경 방향 선택 로직(양쪽 여유 시 랜덤)을 포함한다 방향 선택 로직(좌측 우선; 좌측이 막힌 경우에만 우측)을 포함한다. 추가 rotateLeft()로 우측 스캔 후 정면 복귀한다.
CleaningController CleaningMotor를 제어하고 HighPowerTimer를 관리한다
HighPowerTimer 고출력 청소 지속 시간을 관리하고 만료 시 CleaningController에 통보한다

열거형

클래스 사용처
Direction LEFT, RIGHT NavigationController가 DriveMotor 회전 방향 결정 시 사용
CleaningLevel NORMAL, HIGH CleaningController가 CleaningMotor 출력 지정 시 사용

SRP 적용 근거

기존 문제 분리 후
RVCController가 이벤트 수신 + 주행 제어 + 청소 제어 + 타이머 관리를 모두 담당 변경 이유가 4가지 → SRP 위반 RVCController(이벤트 수신·위임), NavigationController(주행), CleaningController(청소+타이머) 로 분리
설계 — 클래스 다이어그램
arch/design/class/class-diagram.md
class-diagramv2.0 수정

클래스 다이어그램 (Class Diagram)

다이어그램

start()

notifies

notifies

delegates

delegates

controls

controls

owns

notifies

uses

uses

«actor»

User

«actor»

ObstacleSensor

«actor»

DustSensor

«actor»

DriveMotor

«actor»

CleaningMotor

«interface»

IDriveMotor

+moveForward() : void

+moveBackward() : void

+stop() : void

+turn(direction: Direction) : void

+rotateRight() : void

+rotateLeft() : void

«interface»

ICleaningMotor

+startCleaning(level: CleaningLevel) : void

+stopCleaning() : void

«interface»

ITimerExpiredCallback

+onExpired() : void

RVCController

+start() : void

+frontObstacleDetected() : void

+sideStatus(direction: Direction, status: bool) : void

+dustDetected() : void

+dustDetected(dustLevel: float) : void

+changeState(newState: int) : void

NavigationController

+moveForward() : void

+moveBackward() : void

+stop() : void

+turn(direction: Direction) : void

+rotateRight() : void

+rotateLeft() : void

CleaningController

+startCleaning() : void

+stopCleaning() : void

+boostCleaning() : void

+onExpired() : void

+normalizePower() : void

HighPowerTimer

-duration: int

+start() : void

+reset() : void

+stop() : void

«enumeration»

Direction

LEFT

RIGHT

«enumeration»

CleaningLevel

NORMAL

HIGH

클래스 책임 요약

외부 액터

클래스 역할
User start() 호출로 시스템을 시작시키는 주체
ObstacleSensor 변경 frontObstacleDetected(), sideStatus(available), allSidesBlocked() 이벤트를 전달한다 frontObstacleDetected(), sideStatus(LEFT, status), sideStatus(RIGHT, status) 이벤트를 전달한다. 우측은 rotateRight() 이후 전방 센서가 전달한다 (우측 전용 센서 없음).
DustSensor dustDetected() 이벤트를 전달한다
DriveMotor IDriveMotor를 구현한다. NavigationController의 주행 명령을 수행한다
CleaningMotor ICleaningMotor를 구현한다. CleaningController의 청소 출력 명령을 수행한다

인터페이스

클래스 역할
IDriveMotor 주행 모터 추상화. DIP 적용으로 NavigationController와 DriveMotor의 직접 의존을 차단한다
ICleaningMotor 청소 모터 추상화. DIP 적용으로 CleaningController와 CleaningMotor의 직접 의존을 차단한다
ITimerExpiredCallback 타이머 만료 콜백 추상화. HighPowerTimer↔CleaningController 순환 의존을 차단한다

시스템 내부 클래스

클래스 책임
RVCController 시스템 오퍼레이션을 수신하고 NavigationController·CleaningController에 위임한다. sideStatus(LEFT)·sideStatus(RIGHT) 결과로 회피 방향을 결정한다 (LEFT 여유 시 LEFT 우선; LEFT 막힘 시에만 RIGHT). 추가 dustDetected(float) — 음수 입력 시 무시하는 오버로드. 추가 changeState(int) — 0(IDLE)/1(CLEANING) 범위 외 값 및 동일 상태 전이 무시.
NavigationController 변경 IDriveMotor를 제어한다. turn(available)에서 Direction 목록을 받아 방향 선택 로직(양쪽 여유 시 랜덤)을 수행한다 IDriveMotor를 제어한다. turn(direction: Direction)으로 단일 방향을 받아 회전한다. rotateRight()로 전방 센서를 우측으로 회전시킨다. 방향 선택(좌측 우선)은 RVCController가 결정한다.
CleaningController ICleaningMotor 출력을 제어하고 HighPowerTimer를 관리한다. ITimerExpiredCallback을 구현하며 onExpired() 수신 시 출력을 NORMAL로 복구한다. 추가 normalizePower() — BOOST 상태에서 즉시 NORMAL로 복귀(비활성 및 비부스트 상태 시 무시).
HighPowerTimer 고출력 청소 지속 시간을 관리한다. 만료 시 ITimerExpiredCallback.onExpired()를 호출한다

열거형

클래스 사용처
Direction LEFT, RIGHT NavigationController가 DriveMotor 회전 방향 결정 시 사용
CleaningLevel NORMAL, HIGH CleaningController가 CleaningMotor 출력 지정 시 사용

메서드 출처 (SD 추적)

메서드 출처 SD
RVCController.start() SD-001
RVCController.frontObstacleDetected() SD-002
변경 RVCController.sideStatus(available: List<Direction>) RVCController.sideStatus(direction: Direction, status: bool) SD-002, SD-003
삭제 RVCController.allSidesBlocked() SD-003
RVCController.dustDetected() SD-004
NavigationController.moveForward() SD-001, SD-002, SD-003
NavigationController.moveBackward() SD-003
NavigationController.stop() SD-002, SD-003
변경 NavigationController.turn(available: List<Direction>) NavigationController.turn(direction: Direction) SD-002, SD-003
추가 NavigationController.rotateRight() SD-002, SD-003
추가 IDriveMotor.rotateRight() SD-002, SD-003
추가 NavigationController.rotateLeft() SD-002, SD-003
추가 IDriveMotor.rotateLeft() SD-002, SD-003
CleaningController.startCleaning() SD-001, SD-002, SD-003
CleaningController.stopCleaning() SD-002, SD-003
CleaningController.boostCleaning() SD-004
CleaningController.onExpired() SD-004
HighPowerTimer.start() SD-004
HighPowerTimer.reset() SD-004
HighPowerTimer.stop() SD-002, SD-003, SD-004
추가 RVCController.dustDetected(float dustLevel) — (음수 입력 무시 오버로드, 시나리오 테스트 지원)
추가 RVCController.changeState(int newState) — (유효 범위 외·동일 상태 전이 무시, 시나리오 테스트 지원)
추가 CleaningController.normalizePower() — (BOOST → NORMAL 즉시 복귀, 시나리오 테스트 지원)
설계 — SD
arch/design/sd/SD-001.md
SD-001

SD-001: 자동 청소 수행

  • 연관 UC: UC-001
  • 연관 SSD: SSD-001

시퀀스 다이어그램

CleaningControllerNavigationControllerRVCControllerCleaningControllerNavigationControllerRVCController직진하며 청소 유지loop[청소 진행 중]UserCleaningMotorDriveMotorstart()startCleaning()startCleaning(NORMAL)moveForward()moveForward()UserCleaningMotorDriveMotor
arch/design/sd/SD-002.md
SD-002v2.0 수정

SD-002: 측면 장애물 회피

  • 연관 UC: UC-002
  • 연관 SSD: SSD-002

시퀀스 다이어그램

HighPowerTimerCleaningControllerNavigationControllerRVCControllerHighPowerTimerCleaningControllerNavigationControllerRVCControllerUC-001 실행 중방향 결정: sideStatus(LEFT) clear → turn(LEFT) 우선LEFT 막힘 시에만 sideStatus(RIGHT) 결과로 turn(RIGHT)UC-003으로 전환alt[[E1] 좌·우 모두 막힘][좌측 여유 → LEFT 우선][[변경] 우측만 여유]UC-001 기본 흐름 복귀ObstacleSensorCleaningMotorDriveMotorfrontObstacleDetected()stopCleaning()stop()stopCleaning()stop()stop()sideStatus(LEFT, status)rotateRight()rotateRight()sideStatus(RIGHT, status)rotateLeft()rotateLeft()turn(LEFT)turn(LEFT)startCleaning()startCleaning(NORMAL)moveForward()moveForward()turn(RIGHT)turn(RIGHT)startCleaning()startCleaning(NORMAL)moveForward()moveForward()ObstacleSensorCleaningMotorDriveMotor

설계 노트

  • 변경 NavigationController.turn(available)Direction 목록을 받아 방향 선택 로직(랜덤 포함)을 NavigationController 내부에서 수행한다 (SRP) NavigationController.turn(direction: Direction) — 단일 방향을 받아 회전한다. 방향 선택(좌측 우선)은 RVCController가 두 sideStatus 결과로 결정한 뒤 전달한다.
  • 변경 예외 흐름 E1 (좌·우 모두 막힘): sideStatus([]) 수신 시(available이 빈 리스트) RVCController가 UC-003 흐름으로 전환한다 예외 흐름 E1: sideStatus(LEFT, blocked) AND sideStatus(RIGHT, blocked) 수신 시 RVCController가 UC-003 흐름으로 전환한다.
  • 추가 rotateRight() — RVCController → NavigationController → DriveMotor 순으로 위임하여 전방 센서를 우측으로 회전시킨다. sideStatus(LEFT) 수신 직후, sideStatus(RIGHT) 수신 직전에 호출한다.
  • 추가 rotateLeft() — sideStatus(RIGHT) 수신 직후 정면으로 복귀한다. 이후 turn(LEFT)/turn(RIGHT)이 모두 원래 전방 기준으로 대칭 동작하도록 보장한다.
arch/design/sd/SD-003.md
SD-003v2.0 수정

SD-003: 삼면 장애물 후진 회피

  • 연관 UC: UC-003
  • 연관 SSD: SSD-003

시퀀스 다이어그램

CleaningControllerNavigationControllerRVCControllerCleaningControllerNavigationControllerRVCControllerUC-002 E1 진입 (좌·우 모두 막힘 확인됨)alt[좌측 막힘]loop[좌 또는 우 여유 공간 확보 시까지]방향 결정: LEFT clear → turn(LEFT) 우선LEFT 막힘 시에만 sideStatus(RIGHT) 결과로 turn(RIGHT)alt[[변경] 좌측 여유 (좌측 우선)][[변경] 우측만 여유]UC-001 기본 흐름 복귀ObstacleSensorCleaningMotorDriveMotormoveBackward()moveBackward()sideStatus(LEFT, status)rotateRight()rotateRight()sideStatus(RIGHT, status)rotateLeft()rotateLeft()stop()stop()turn(LEFT)turn(LEFT)turn(RIGHT)turn(RIGHT)moveForward()moveForward()startCleaning()startCleaning(NORMAL)ObstacleSensorCleaningMotorDriveMotor

설계 노트

  • 변경 청소 재개(startCleaning)는 전진 시작 이후에 호출한다 (UC-003 기본 흐름 8→9 순서) 청소 재개(startCleaning)는 전진(moveForward) 이후에 호출한다 (UC-003 기본 흐름 9→10 순서).
  • 변경 후진 중 루프를 탈출하는 조건: available이 비어 있지 않을 때 후진 중 루프 탈출 조건: sideStatus(LEFT, clear) 또는 sideStatus(RIGHT, clear) 중 하나라도 수신될 때.
  • 변경 방향 선택 로직은 SD-002와 동일하게 NavigationController 내부에서 수행한다 방향 선택(좌측 우선)은 RVCController가 sideStatus 결과로 결정하고 NavigationController에 단일 방향을 전달한다.
  • 추가 UC-003은 UC-002 E1에서 진입한다. CleaningMotor는 UC-002에서 이미 정지된 상태이므로 이 SD에서 별도의 stopCleaning 호출이 없다.
  • 삭제 allSidesBlocked() — ObstacleSensor → RVCController 외부 호출 (UC-002 내 두 sideStatus 결과로 내부 판정)
  • 추가 후진 중 좌측이 막혀있을 때만 rotateRight() → sideStatus(RIGHT) → rotateLeft() 순으로 우측을 확인하고 정면 복귀한다. 좌측 clear 시 즉시 좌회전하며 우측 확인을 생략한다.
arch/design/sd/SD-004.md
SD-004

SD-004: 고출력 청소

  • 연관 UC: UC-004
  • 연관 SSD: SSD-004

시퀀스 다이어그램

HighPowerTimerCleaningControllerRVCControllerHighPowerTimerCleaningControllerRVCControllerUC-001 실행 중opt[먼지 재감지]loop[고출력 유지 중]UC-001 기본 흐름 복귀DustSensorCleaningMotordustDetected()boostCleaning()startCleaning(HIGH)start()dustDetected()boostCleaning()reset()onExpired()startCleaning(NORMAL)DustSensorCleaningMotor

설계 노트

  • DriveMotor는 UC-004 실행 중 주행 명령이 변경되지 않으므로 이 SD에 등장하지 않는다
  • HighPowerTimerCleaningController가 composition으로 소유한다 (도메인 모델 참조)
  • boostCleaning() 재호출 시 CleaningMotor에는 추가 명령을 보내지 않고 HighPowerTimer만 리셋한다
구현
src/code.md
codev2.0 수정

src/ 코드 변경 이력 — v2.0

변경 태그: 추가 / 삭제 (취소선) / 변경 단위 테스트(tests/)·시스템 테스트(system-tests/) 파일은 별도 관리하며 이 문서에 포함하지 않는다. 변경이 없는 파일은 목록 하단 변경 없음 절에 나열한다.


IDriveMotor.hpp

구분 항목 내용
추가 rotateRight() = 0 전방 센서를 우측으로 회전시키기 위한 순수 가상 메서드 추가
추가 rotateLeft() = 0 우측 스캔 후 정면으로 복귀하기 위한 순수 가상 메서드 추가
// [추가]
virtual void rotateRight() = 0;
virtual void rotateLeft()  = 0;

NavigationController.hpp

구분 항목 내용
삭제 #include <vector> turn() 시그니처 변경으로 vector 불필요
변경 turn(const std::vector<Direction>&)turn(Direction direction) 단일 방향 인자로 단순화
추가 rotateRight() IDriveMotor::rotateRight() 위임 메서드
추가 rotateLeft() IDriveMotor::rotateLeft() 위임 메서드
추가 MotorState enum 모터 상태 추적을 위한 내부 열거형 (STOPPED, FORWARD, BACKWARD, LEFT, RIGHT, ROTATING_RIGHT, ROTATING_LEFT)
추가 MotorState motorState_ 현재 모터 상태 멤버 변수 (초기값 STOPPED)
// [변경] 이전: void turn(const std::vector<Direction>& directions);
void turn(Direction direction);   // [변경]
void rotateRight();               // [추가]
void rotateLeft();                // [추가]

// [추가]
enum class MotorState {
    STOPPED, FORWARD, BACKWARD, LEFT, RIGHT, ROTATING_RIGHT, ROTATING_LEFT
};
MotorState motorState_ = MotorState::STOPPED;

NavigationController.cpp

구분 항목 내용
삭제 #include <vector>, #include <random> 랜덤 선택 로직 제거로 헤더 불필요
변경 turn() 구현 벡터를 받아 랜덤으로 방향 선택 → 단일 방향을 motor_.turn(direction)에 위임. 중복 호출 및 FORWARD/BACKWARD 중 전환 시도를 거부하는 MotorState guard 추가
추가 rotateRight() 구현 motor_.rotateRight() 위임
추가 rotateLeft() 구현 motor_.rotateLeft() 위임
변경 moveForward() 구현 이미 FORWARD 상태면 중복 호출 무시 (MotorState guard)
변경 moveBackward() 구현 이미 BACKWARD 상태면 중복 호출 무시 (MotorState guard)
변경 stop() 구현 이미 STOPPED 상태면 중복 호출 무시 (MotorState guard)
// [변경] 모든 메서드에 MotorState 추적 및 중복/무효 guard 적용
void NavigationController::moveForward() {
    if (motorState_ == MotorState::FORWARD) return;
    motor_.moveForward(); motorState_ = MotorState::FORWARD;
}

void NavigationController::stop() {
    if (motorState_ == MotorState::STOPPED) return;
    motor_.stop(); motorState_ = MotorState::STOPPED;
}

void NavigationController::turn(Direction direction) {
    MotorState target = (direction == Direction::LEFT) ? MotorState::LEFT : MotorState::RIGHT;
    if (motorState_ == target) return;
    if (motorState_ == MotorState::FORWARD || motorState_ == MotorState::BACKWARD) return;
    motor_.turn(direction); motorState_ = target;
}

// [추가]
void NavigationController::rotateRight() { motor_.rotateRight(); }
void NavigationController::rotateLeft()  { motor_.rotateLeft();  }

CleaningController.hpp

구분 항목 내용
추가 normalizePower() BOOST → NORMAL 강제 복귀 (비활성·비부스트 상태 시 무시)
추가 bool active_ 청소 활성화 여부 추적 멤버 변수 (초기값 false)
// [추가]
void normalizePower();  // BOOST → NORMAL (중복 무시, OFF 시 거부)

// [추가]
bool active_;

CleaningController.cpp

구분 항목 내용
변경 startCleaning() 이미 활성 상태(active_==true)면 중복 호출 무시. active_=true 설정 추가
변경 stopCleaning() 비활성 상태(active_==false)면 중복 호출 무시. active_=false 설정 추가
변경 boostCleaning() 비활성 상태(active_==false)면 호출 거부
추가 normalizePower() 구현 비활성 또는 비부스트 상태면 무시. 활성+부스트 중일 때 타이머 중단 후 NORMAL 복귀
// [변경]
void CleaningController::startCleaning() {
    if (active_) return;   // [추가] 중복 guard
    active_ = true;        // [추가]
    timer_.stop(); boosting_ = false;
    motor_.startCleaning(CleaningLevel::NORMAL);
}

void CleaningController::stopCleaning() {
    if (!active_) return;  // [추가] 비활성 guard
    active_ = false;       // [추가]
    timer_.stop(); boosting_ = false;
    motor_.stopCleaning();
}

void CleaningController::boostCleaning() {
    if (!active_) return;  // [추가] 비활성 guard
    if (boosting_) { timer_.reset(); }
    else { boosting_ = true; motor_.startCleaning(CleaningLevel::HIGH); timer_.start(); }
}

// [추가]
void CleaningController::normalizePower() {
    if (!active_) return;
    if (!boosting_) return;
    timer_.stop(); boosting_ = false;
    motor_.startCleaning(CleaningLevel::NORMAL);
}

RVCController.hpp

구분 항목 내용
삭제 #include <vector> sideStatus() 시그니처 변경으로 불필요
삭제 void allSidesBlocked() 전방·좌·우 동시 막힘 판정을 sideStatus() 내부 상태머신으로 대체
변경 sideStatus(const std::vector<Direction>&)sideStatus(Direction direction, bool clear) 방향별 상태를 순차 호출로 분리
변경 State enum IDLE, CLEANING, REVERSING 3개 → 6개 상태로 확장
추가 State::STOPPED frontObstacleDetected() 수신 후 좌측 스캔 대기 상태
추가 State::STOPPED_CHECKING_RIGHT sideStatus(LEFT) 수신 후 rotateRight() 호출, 우측 스캔 대기 상태
추가 State::REVERSING_CHECKING_RIGHT 후진 중 sideStatus(LEFT) 막힘 확인 후 rotateRight(), 우측 스캔 대기 상태
추가 bool leftClear_ STOPPEDSTOPPED_CHECKING_RIGHT 전이 시 좌측 결과 임시 보관
추가 dustDetected(float dustLevel) 먼지 수준 float 파라미터 오버로드. 음수 입력 시 무시
추가 changeState(int newState) 외부에서 상태 직접 지정 (0=IDLE, 1=CLEANING). 범위 외 값 및 동일 상태 전이 무시
// [변경] 이전: void allSidesBlocked();  ← [삭제]
// [변경] 이전: void sideStatus(const std::vector<Direction>& clearDirections);
void sideStatus(Direction direction, bool clear);  // [변경]
void dustDetected(float dustLevel);                // [추가]
void changeState(int newState);                    // [추가]

enum class State {
    IDLE,
    CLEANING,
    STOPPED,                   // [추가]
    STOPPED_CHECKING_RIGHT,    // [추가]
    REVERSING,
    REVERSING_CHECKING_RIGHT   // [추가]
};

bool leftClear_;  // [추가]

RVCController.cpp

구분 항목 내용
삭제 allSidesBlocked() 구현 삭제. 좌·우 모두 막힘은 STOPPED_CHECKING_RIGHT 상태에서 내부 판정
변경 sideStatus() 시그니처 const std::vector<Direction>&Direction direction, bool clear
변경 sideStatus() 내부 로직 방향 목록 기반 단순 분기 → 4개 상태에 걸친 순차 상태머신으로 전면 재작성
추가 STOPPED 분기 leftClear_ 저장 후 rotateRight() 호출, STOPPED_CHECKING_RIGHT 전이
추가 STOPPED_CHECKING_RIGHT 분기 rotateLeft() 정면 복귀 후 LEFT 우선 방향 결정. 양쪽 막힘 시 moveBackward()REVERSING
추가 REVERSING 분기 좌측 여유 시 즉시 stop+turn(LEFT)+forward. 막힘 시 rotateRight()REVERSING_CHECKING_RIGHT
추가 REVERSING_CHECKING_RIGHT 분기 rotateLeft() 복귀 후 우측 여유 시 stop+turn(RIGHT)+forward. 막힘 시 REVERSING 복귀(계속 후진)
변경 start() 구현 CLEANING·REVERSING·REVERSING_CHECKING_RIGHT 상태에서 중복 호출 무시 guard 추가
변경 frontObstacleDetected() 구현 REVERSING·REVERSING_CHECKING_RIGHT 상태(후진 중)에서 재감지 무시 guard 추가
추가 dustDetected(float) 구현 음수(< 0.0f) 입력 시 무시. 그 외 cleaning_.boostCleaning() 위임
추가 changeState(int) 구현 유효 범위(0~1) 외 및 동일 상태 전이 무시. 0=IDLE, 1=CLEANING으로 직접 state_ 설정

상태 전이 요약

frontObstacleDetected()
  CLEANING → STOPPED

sideStatus(LEFT, clear)  [STOPPED]
  → rotateRight()
  → STOPPED_CHECKING_RIGHT  (leftClear_ 저장)

sideStatus(RIGHT, clear)  [STOPPED_CHECKING_RIGHT]
  → rotateLeft()
  → leftClear_==true  : turn(LEFT) → CLEANING
  → clear==true       : turn(RIGHT) → CLEANING
  → 둘 다 false       : moveBackward() → REVERSING   ← E1 진입

sideStatus(LEFT, clear)  [REVERSING]
  → clear==true  : stop → turn(LEFT) → CLEANING
  → clear==false : rotateRight() → REVERSING_CHECKING_RIGHT

sideStatus(RIGHT, clear)  [REVERSING_CHECKING_RIGHT]
  → rotateLeft()
  → clear==true  : stop → turn(RIGHT) → CLEANING
  → clear==false : REVERSING (계속 후진)

변경 없음

아래 파일은 v2.0에서 수정되지 않았다.

파일 역할
Direction.hpp LEFT / RIGHT 열거형
CleaningLevel.hpp NORMAL / HIGH 열거형
ICleaningMotor.hpp 청소 모터 인터페이스
ITimerExpiredCallback.hpp 타이머 만료 콜백 인터페이스
HighPowerTimer.hpp 고출력 타이머 헤더
HighPowerTimer.cpp 고출력 타이머 구현
테스트
tests/unit_test.md
unit_testv2.0 추가

단위 테스트 변경 이력

전체 요약

파일v1.0 테스트 수v2.0 테스트 수변경
CleaningControllerTest.cpp69선행 상태 추가, normalizePower 신규
NavigationControllerTest.cpp87시그니처 변경 반영, 신규 추가
RVCControllerTest.cpp1632대폭 재작성, 커버리지 보완 추가
합계3048

CleaningControllerTest.cpp

변경 배경

CleaningControlleractive_ 상태 추적 및 guard 로직이 추가되면서, 비활성 상태에서 stopCleaning() / boostCleaning()을 호출하면 guard에 막혀 모터가 실제로 호출되지 않는다. 기존 테스트는 선행 상태(precondition) 없이 메서드를 바로 호출하고 있었으므로 수정이 필요했다.

또한 normalizePower() 신규 추가에 따라 대응 테스트 3개가 추가됐다. boostCleaning_whenAlreadyBoosting_doesNotCallMotorAgainstartCleaning() 선행 없이 호출해 active_==false guard에 막혀 timer_.reset() 분기에 도달하지 못했으므로 선행 상태를 추가해 라인 커버리지 100%를 달성했다.

변경 내용

구분테스트내용
변경stopCleaning_callsMotorStopcontroller.startCleaning() 선행 호출 추가 — active_: false → true
변경boostCleaning_whenIdle_switchesToHighLevelcontroller.startCleaning()motor.startCalls.clear() 선행 추가
변경timerExpiry_restoresNormal_andAllowsSubsequentBoostcontroller.startCleaning() 선행 호출 추가
변경boostCleaning_whenAlreadyBoosting_doesNotCallMotorAgaincontroller.startCleaning() 선행 추가 — active_==false로 인해 timer_.reset() 분기 미도달 수정

추가

구분테스트검증 내용
추가normalizePower_whenBoosting_restoresNormal부스트 중 normalizePower() → NORMAL 복귀 및 모터 호출 확인
추가normalizePower_whenNotBoosting_isIgnored비부스트 상태에서 normalizePower() 무시 — 모터 재호출 없음
추가normalizePower_whenInactive_isIgnored비활성(active_==false) 상태에서 normalizePower() 무시

변경 없음

테스트설명
startCleaning_callsMotorWithNormalLevelNORMAL 레벨로 모터 호출 검증
onExpired_restoresNormalLevel타이머 만료 시 NORMAL 복귀 검증

NavigationControllerTest.cpp

변경 배경

v2.0에서 turn(const std::vector<Direction>&) 시그니처가 turn(Direction) 단일 방향으로 변경되고, rotateRight() / rotateLeft() 메서드가 추가됐다. 이에 따라 벡터 기반 테스트를 삭제하고 단일 방향 기반으로 재작성했다.

또한 MotorState guard 추가로 인해 stop_delegatesToMotor가 초기 STOPPED 상태에서 stop()을 호출하면 guard에 막히게 되어 선행 상태 설정이 필요해졌다.

변경 내용

구분테스트내용
삭제turn_emptyList_doesNotCallMotor벡터 기반 — 빈 목록 시 모터 미호출 검증. 시그니처 변경으로 삭제
삭제turn_singleLeft_turnsLeft벡터 기반 단일 LEFT 검증. 삭제 후 아래로 대체
삭제turn_singleRight_turnsRight벡터 기반 단일 RIGHT 검증. 삭제 후 아래로 대체
삭제turn_multipleDirections_picksExactlyOne랜덤 방향 선택 검증. v2.0에서 랜덤 제거로 삭제
삭제turn_calledTwice_motorCalledTwice2회 연속 turn 검증. MotorState guard로 동작 변경되어 삭제
추가turn_left_delegatesToMotorturn(Direction::LEFT) 단일 방향 위임 검증
추가turn_right_delegatesToMotorturn(Direction::RIGHT) 단일 방향 위임 검증
추가rotateRight_delegatesToMotorrotateRight() → 모터 위임 검증
추가rotateLeft_delegatesToMotorrotateLeft() → 모터 위임 검증
변경stop_delegatesToMotorcontroller.moveForward() 선행 추가 — motorState_: STOPPED → FORWARD

변경 없음

테스트설명
moveForward_delegatesToMotormoveForward() → 모터 위임 검증
moveBackward_delegatesToMotormoveBackward() → 모터 위임 검증

RVCControllerTest.cpp

변경 배경

v2.0에서 다음이 변경되었다:

  • sideStatus(const vector<Direction>&)sideStatus(Direction, bool) 시그니처 변경
  • allSidesBlocked() 오퍼레이션 삭제 (내부 상태머신으로 대체)
  • STOPPED, STOPPED_CHECKING_RIGHT, REVERSING_CHECKING_RIGHT 상태 추가
  • rotateRight() / rotateLeft() 호출 흐름 추가
  • 회피 방향 결정이 랜덤 → 좌측 우선으로 변경

이에 따라 기존 16개 테스트 전반이 재작성되고 10개가 추가됐다. 또한 신규 추가 메서드(dustDetected(float), changeState(int))의 커버리지 보완을 위해 6개 테스트가 추가됐다.

삭제

테스트삭제 이유
sideStatus_whenStopped_turnsToAvailableDirection벡터 기반 시그니처 → 방향별 2단계 호출로 대체
sideStatus_whenStopped_restartsCleaning동일
sideStatus_whenStopped_movesForward동일
allSidesBlocked_stopsCleaningallSidesBlocked() 오퍼레이션 삭제
allSidesBlocked_movesBackward동일
sideStatus_whenReversing_withAvailableDir_stopsMotor벡터 기반 — 좌측 우선 로직으로 전면 재작성
sideStatus_whenReversing_withAvailableDir_turns동일
sideStatus_whenReversing_withAvailableDir_movesForward동일
sideStatus_whenReversing_withAvailableDir_restartsCleaning동일
sideStatus_whenReversing_withEmptyDir_isIgnored동일

추가

테스트검증 내용
sideStatus_firstCall_rotatesRightSTOPPED 상태에서 첫 sideStatus(LEFT) 수신 시 rotateRight() 호출
sideStatus_secondCall_rotatesLeftSTOPPED_CHECKING_RIGHT에서 sideStatus(RIGHT) 수신 시 rotateLeft() 호출
sideStatus_leftClear_turnsLeft좌측 여유 시 LEFT 방향으로 회전
sideStatus_leftClear_restartsCleaning좌측 여유 시 청소 재개
sideStatus_leftClear_movesForward좌측 여유 시 전진
sideStatus_leftBlockedRightClear_turnsRight좌측 막힘·우측 여유 시 RIGHT 방향으로 회전
sideStatus_leftBlockedRightClear_restartsCleaning좌측 막힘·우측 여유 시 청소 재개
sideStatus_leftBlockedRightClear_movesForward좌측 막힘·우측 여유 시 전진
sideStatus_bothBlocked_movesBackward좌우 모두 막힘 시 후진 (E1 진입)
sideStatus_bothBlocked_doesNotTurn좌우 모두 막힘 시 turn 미호출
sideStatus_whenReversing_leftClear_stops후진 중 좌측 여유 시 stop
sideStatus_whenReversing_leftClear_turnsLeft후진 중 좌측 여유 시 LEFT 회전
sideStatus_whenReversing_leftClear_movesForward후진 중 좌측 여유 시 전진
sideStatus_whenReversing_leftClear_restartsCleaning후진 중 좌측 여유 시 청소 재개
sideStatus_whenReversing_leftBlocked_rotatesRight후진 중 좌측 막힘 시 rotateRight() 호출
sideStatus_whenReversing_rightOnly_turnsRight후진 중 우측만 여유 시 RIGHT 회전
sideStatus_whenReversing_rightOnly_movesForward후진 중 우측만 여유 시 전진
sideStatus_whenReversing_rightOnly_restartsCleaning후진 중 우측만 여유 시 청소 재개
sideStatus_whenReversing_bothBlocked_noTurnOrForward후진 중 좌우 모두 막힘 시 turn/forward 미호출
sideStatus_whenReversing_bothBlocked_rotatesRightThenLeft후진 중 좌우 막힘 시 rotateRightrotateLeft 호출
dustDetected_positiveLevel_boostsCleaningdustDetected(float) 양수 입력 → HIGH 전환 확인
dustDetected_negativeLevel_isIgnoreddustDetected(float) 음수 입력 → 무시, 모터 미호출
changeState_idleToCleaning_changesStatechangeState(1) → CLEANING 전이 후 start() 중복 무시 확인
changeState_cleaningToIdle_changesStatechangeState(0) → IDLE 전이 후 start() 정상 동작 확인
changeState_outOfRange_isIgnored범위 외 값(999, -1) 입력 시 상태 변화 없음
changeState_sameState_isIgnored동일 상태 전이 무시 — 모터 재호출 없음

변경 없음

테스트설명
start_startsCleaning_atNormalLevel시작 시 NORMAL 레벨로 청소 시작
start_movesForward시작 시 전진
frontObstacleDetected_stopsCleaning전방 장애물 감지 시 청소 정지
frontObstacleDetected_stopsMotor전방 장애물 감지 시 모터 정지
sideStatus_whenCleaning_isIgnoredCLEANING 상태에서 sideStatus 무시
dustDetected_boostsCleaningToHighLevel먼지 감지 시 HIGH 레벨 전환
system-tests/system_test.md
system_testv2.0 추가

시스템 테스트 변경 이력

전체 요약

파일상태시나리오 수비고
SystemTests.cpp추가30 (POS 10 + NEG 20)v2.0 핵심 시나리오
SystemTests_v2.cpp추가30 (POS 10 + NEG 20)UP팀 제공 시나리오

v1.0에는 시스템 테스트 파일이 존재하지 않았다. 두 파일 모두 v2.0에서 신규 추가.


SystemTests.cpp — v2.0 핵심 시나리오

긍정 시나리오 (정상 흐름)

ID시나리오검증 내용
ST-P01자동 청소 시작startCleaning(NORMAL) + moveForward() 호출 순서
ST-P02전방 장애물 감지stopCleaning() + stop() 호출
ST-P03장애물 회피 우측좌측 막힘·우측 여유 시 turn(RIGHT)startCleaningforward 완전 시퀀스
ST-P04장애물 회피 좌측좌측 여유 시 turn(LEFT)startCleaningforward 완전 시퀀스
ST-P05좌·우 모두 여유LEFT 우선 선택 (v2.0 정책 검증)
ST-P06삼면 장애물 후 우측 전진E1 진입 후 후진 중 우측 여유 확보 시 turn(RIGHT) → 전진
ST-P07삼면 장애물 후 좌측 전진E1 진입 후 후진 중 좌측 여유 확보 시 turn(LEFT) → 전진
ST-P08삼면 장애물 — 좌우 반복 막힘 후 방향 확보여러 번 막힘 후 최종 탈출
ST-P09먼지 감지 HIGH 전환startCleaning(HIGH) 호출 확인
ST-P10먼지 감지 후 타이머 만료NORMAL 자동 복귀 (실제 타이머 대기)

부정 시나리오 (비정상 입력·중복 호출 무시 검증)

ID시나리오검증 내용
ST-N01IDLE에서 sideStatus(LEFT, true)무시 — 모터 미호출
ST-N02IDLE에서 sideStatus(LEFT, false)무시 — 모터 미호출
ST-N03CLEANING에서 sideStatus(LEFT, true)무시 — CLEANING 상태는 sideStatus 처리 안 함
ST-N04CLEANING에서 sideStatus(LEFT, false)무시
ST-N05REVERSING에서 좌우 막힘stop / turn / forward 미호출
ST-N06REVERSING에서 좌우 막힘 3회forward 0회 확인
ST-N07dustDetected 2회startCleaning(HIGH) 1회만 호출 (중복 boost 무시)
ST-N08dustDetectedDriveMotor 호출 전혀 없음 확인
ST-N09frontObstacleDetectedstartCleaning 미호출 확인
ST-N10E1 진입startCleaning 미호출 (allSidesBlocked 제거 후 내부 판정 검증)
ST-N11좌·우 모두 여유turn 1회, LEFT 선택
ST-N12UC-002 호출 순서stopCleaningstop 순서 보장
ST-N13UC-002 호출 순서turnstartCleaning(NORMAL) 순서 보장
ST-N14UC-003 호출 순서forwardstartCleaning(NORMAL) 순서 보장
ST-N15start() 중복 — CLEANING 상태무시
ST-N16start() 중복 — REVERSING 상태무시
ST-N17E1 진입stopCleaning 1회, moveBackward 1회 확인
ST-N18REVERSING 중 frontObstacleDetected재감지 무시 — 후진 유지
ST-N19장애물 2회 연속turn 총 2회
ST-N20고출력 중 장애물 감지stop 1회, 마지막 모터 호출이 NORMAL

SystemTests_v2.cpp — UP팀 제공 시나리오

긍정 시나리오 (정상 흐름)

ID시나리오검증 내용
TC-POS-01좌측 회피 전체 흐름전방 장애물 → 좌측 여유 → turn(LEFT) → 재개 전체 시퀀스
TC-POS-02고먼지 집중 청소 → 타이머 만료 → NORMAL 복귀HIGH → 자동 NORMAL 복귀
TC-POS-03후진 후 좌측 탈출삼면 막힘 → 후진 → 좌측 여유 시 탈출
TC-POS-04좌측 막힘·우측 뚫림 → turn(RIGHT)좌측 막힘 시 우측 선택 정책
TC-POS-05집중 청소 복귀 후 장애물 → turn(RIGHT)NORMAL 복귀 후 장애물 발생 시나리오
TC-POS-062회 연속 회피 (1차 LEFT·2차 RIGHT)회피 방향 교대 시나리오
TC-POS-072회 연속 집중 청소 → 각 NORMAL 복귀boost 후 NORMAL 복귀 2회 반복
TC-POS-08저먼지 미발생 → 일반 청소 유지dustDetected 미호출 시 NORMAL 유지
TC-POS-09전체 순차: 먼지 → 장애물 → 후진 → 탈출복합 시나리오 end-to-end
TC-POS-10전방 개방 → 직진 유지sideStatus 없을 때 forward 유지

부정 시나리오 (guard·경계값·중복 무시 검증)

ID시나리오검증 내용
TC-NEG-01CLEANING 중 start() 중복무시 — 모터 추가 호출 없음
TC-NEG-02sideStatus 무시 확인부적절한 상태에서 sideStatus 처리 없음
TC-NEG-03changeState(999)범위 외 값 무시
TC-NEG-04AVOIDING 중 start()무시
TC-NEG-05동일 상태 changeState(CLEANING)동일 상태 전이 무시
TC-NEG-06FORWARD 중 turn()MotorState guard — 주행 중 전환 거부
TC-NEG-07AVOIDING 중 장애물 재감지후진 유지 — 재감지 무시
TC-NEG-08FORWARD → moveForward() 중복MotorState guard — 중복 호출 무시
TC-NEG-09BACKWARD → moveBackward() 중복MotorState guard — 중복 호출 무시
TC-NEG-10LEFT → turn(LEFT) 중복MotorState guard — 중복 방향 무시
TC-NEG-11RIGHT → turn(RIGHT) 중복MotorState guard — 중복 방향 무시
TC-NEG-12STOPPED → stop() 중복MotorState guard — 중복 호출 무시
TC-NEG-13음수 dustLeveldustDetected(float) 음수 입력 무시
TC-NEG-14FLT_MAX dustLevel최대값 입력 시 크래시 없음 확인
TC-NEG-15OFF → stopCleaning()active_ guard — 비활성 상태에서 중복 무시
TC-NEG-16NORMAL → startCleaning()active_ guard — 이미 활성 시 중복 무시
TC-NEG-17BOOST → boostCleaning()이미 부스트 중일 때 모터 재호출 없음
TC-NEG-18NORMAL → normalizePower()부스트 상태가 아닐 때 무시
TC-NEG-19OFF → normalizePower()비활성 상태에서 무시
TC-NEG-20OFF → boostCleaning()active_ guard — 비활성 상태에서 거부